// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;

namespace SnapObjects.Data
{
    public class ModelAttributeInfo
    {
        public ModelAttributeInfo(IEnumerable<Attribute> attributes)
        {
            var sqlParameters = new List<ISqlParameter>();
            var sqlWheres = new List<ISqlWhere>();
            var fromTables = new List<IFromTable>();
            var joinTables = new List<IJoinTable>();
            var tables = new List<TableAttribute>();
            var subQueries = new List<ISubQuery>();
            var sqlHavings = new List<ISqlHaving>();
            var sqlUnions = new List<ISqlUnion>();
            var sqlSelects = new Dictionary<string, string>();

            var tableId = 0;

            foreach (var attr in attributes)
            {
                if (this.TryParseAttribute(attr))
                {
                    continue;
                }

                if (attr is FromTableAttribute fromTable)
                {
                    tableId++;
                    fromTable.Id = tableId;
                    fromTables.Add(fromTable);
                    tables.Add(fromTable);
                }
                else if (attr is JoinTableAttribute joinTable)
                {
                    tableId++;
                    joinTable.Id = tableId;
                    joinTables.Add(joinTable);
                    tables.Add(joinTable);
                }
                else if (attr is SqlSelectAttribute sqlSelect)
                {
                    sqlSelects.Add(sqlSelect.SelectName, sqlSelect.RawSelect);
                }
                else if (attr is SqlUnionAttribute sqlUnion)
                {
                    sqlUnions.Add(sqlUnion);
                }
                else if (attr is SubQueryAttribute subQuery)
                {
                    subQueries.Add(subQuery);
                }
                else if (attr is TableAttribute table) //must after joinTable
                {
                    this.TableDefined = true;
                    this.Table = table.Name;
                    this.Schema = table.Schema;
                    tables.Add(table);
                }
                else if (attr is DistinctAttribute)
                {
                    this.Distinct = true;
                }
                else if (attr is TopAttribute top)
                {
                    this.TopDefinded = true;
                    this.TopRows = top.TopRows;
                }
                else if (attr is SqlParameterAttribute sqlParameter)
                {
                    sqlParameters.Add(sqlParameter);
                }
                else if (attr is SqlAndWhereAttribute sqlAndWhere)
                {
                    sqlWheres.Add(new ModelSqlCondition(ModelConditionType.AndWhere, sqlAndWhere));
                }
                else if (attr is SqlOrWhereAttribute sqlOrWhere)
                {
                    sqlWheres.Add(new ModelSqlCondition(ModelConditionType.OrWhere, sqlOrWhere));
                }
                else if (attr is SqlGroupByAttribute sqlGroupBy)
                {
                    this.SqlGroupBy = sqlGroupBy;
                }
                else if (attr is SqlAndHavingAttribute sqlAndHaving)
                {
                    sqlHavings.Add(new ModelSqlCondition(ModelConditionType.AndHaving, sqlAndHaving));
                }
                else if (attr is SqlOrHavingAttribute sqlOrHaving)
                {
                    sqlHavings.Add(new ModelSqlCondition(ModelConditionType.OrHaving, sqlOrHaving));
                }
                else if (attr is SqlHavingAttribute sqlHaving) //must be last, because some attribute inherit from it.
                {
                    sqlHavings.Add(new ModelSqlCondition(ModelConditionType.Having, sqlHaving));
                }
                else if (attr is SqlOrderByAttribute sqlOrderBy)
                {
                    this.SqlOrderBy = sqlOrderBy;
                }
                else if (attr is SqlWhereAttribute sqlWhere) //must be last, because some attribute inherit from it.
                {
                    sqlWheres.Add(new ModelSqlCondition(ModelConditionType.Where, sqlWhere));
                }
                else if (attr is UpdateWhereStrategyAttribute strategy)
                {
                    this.UpdateWhereStrategy = strategy;
                }
            }

            this.SqlParameters = sqlParameters;
            this.SqlWheres = sqlWheres;
            this.FromTables = fromTables;
            this.JoinTables = joinTables;
            this.Tables = tables;
            this.SubQueries = subQueries;
            this.SqlHavings = sqlHavings;
            this.SqlUnions = sqlUnions;
            this.SqlSelects = new ConcurrentDictionary<string, string>(
                                        sqlSelects, StringComparer.OrdinalIgnoreCase);

            this.ValidateAttribute();
        }

        public ModelAttributeInfo(string schema, string table)
        {
            this.Table = table;
            this.Schema = schema;
        }

        protected virtual bool TryParseAttribute(Attribute attribute)
        {
            return false;
        }

        private void ValidateAttribute()
        {
            var tableList = this.FromTables.Where(o => o.Alias != null).Select(o => o.Alias.ToLower()).ToList();
            tableList.AddRange(this.JoinTables.Where(o => o.Alias != null).Select(o => o.Alias.ToLower()));

            if (tableList.Distinct().Count() < tableList.Count())
            {
                throw new InvalidOperationException("Table(JoinTable/FromTable) alias must be unique.");
            }
        }

        public string Table { get; }

        public string Schema { get; }

        public bool TableDefined { get; }

        public IReadOnlyList<ISqlParameter> SqlParameters { get; }

        public IReadOnlyList<IFromTable> FromTables { get; }

        public IReadOnlyList<IJoinTable> JoinTables { get; }

        public IReadOnlyList<TableAttribute> Tables { get; }

        public IReadOnlyList<ISubQuery> SubQueries { get; }

        public IReadOnlyList<ISqlWhere> SqlWheres { get; }

        public IReadOnlyList<ISqlHaving> SqlHavings { get; }

        public ISqlGroupBy SqlGroupBy { get; }

        public ISqlOrderBy SqlOrderBy { get; }

        public IUpdateWhereStrategy UpdateWhereStrategy { get; }

        public IReadOnlyList<ISqlUnion> SqlUnions { get; }

        public IReadOnlyDictionary<string, string> SqlSelects { get; }
        public bool Distinct { get; }

        public bool TopDefinded { get; }

        public int TopRows { get; }
    }
}
